import tkinter as tk
from tkinter import ttk, messagebox, Toplevel
import mysql.connector
import json
import os
from datetime import datetime
import threading
from queue import Queue
import sys

window_width = 700
window_height = 850  # 增加窗口高度以容纳更多表


class CodeGeneratorApp:

    def __init__(self, root):
        self.root = root
        self.root.title("Java代码生成工具")
        self.root.geometry(f"{window_width}x{window_height}")

        # 窗口居中
        self.center_window()
        self.root.lift()  # 启动时将窗口置于顶层
        self.root.focus_force()

        self.db_config = {}
        self.template_dir = "./templates"
        self.config_file = "db_config.json"
        self.conn = None
        self.cursor = None
        self.author = "王冰冰"  # 默认作者
        self.preview_window = None  # 用于跟踪预览窗口
        self.table_vars = {}  # 存储表名和对应的勾选状态
        self.all_tables = []  # 存储所有表数据
        self.search_debounce_id = None  # 用于防抖

        self.load_config()
        self.create_widgets()

    def center_window(self):
        """将窗口居中显示"""
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2)
        self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")

    def load_config(self):
        """加载本地配置"""
        config_path = self.get_config_path()
        if os.path.exists(config_path):
            with open(config_path, 'r', encoding='utf-8') as f:
                self.db_config = json.load(f)

    def save_config(self):
        """保存配置到本地"""
        config_path = self.get_config_path()
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(self.db_config, f, ensure_ascii=False, indent=4)

    def get_config_path(self):
        """获取配置文件路径，打包后使用用户主目录"""
        if getattr(sys, 'frozen', False):  # 打包后的环境
            base_path = os.path.expanduser("~")  # 用户主目录，例如 /Users/username
            return os.path.join(base_path, "db_config.json")
        else:  # 开发环境
            return self.config_file  # 默认 "db_config.json"

    def create_widgets(self):
        """创建界面控件"""
        # MySQL 配置区域
        config_frame = ttk.LabelFrame(self.root, text="MySQL 配置", padding=10)
        config_frame.pack(fill="x", padx=10, pady=5)

        ttk.Label(config_frame, text="Host:").grid(row=0, column=0, padx=5, pady=5)
        self.host_entry = ttk.Entry(config_frame)
        self.host_entry.grid(row=0, column=1, padx=5, pady=5)
        self.host_entry.insert(0, self.db_config.get('host', 'localhost'))

        ttk.Label(config_frame, text="Port:").grid(row=0, column=2, padx=5, pady=5)
        self.port_entry = ttk.Entry(config_frame)
        self.port_entry.grid(row=0, column=3, padx=5, pady=5)
        self.port_entry.insert(0, self.db_config.get('port', '3306'))

        ttk.Label(config_frame, text="User:").grid(row=1, column=0, padx=5, pady=5)
        self.user_entry = ttk.Entry(config_frame)
        self.user_entry.grid(row=1, column=1, padx=5, pady=5)
        self.user_entry.insert(0, self.db_config.get('user', 'root'))

        ttk.Label(config_frame, text="Password:").grid(row=1, column=2, padx=5, pady=5)
        self.pass_entry = ttk.Entry(config_frame)
        self.pass_entry.grid(row=1, column=3, padx=5, pady=5)
        self.pass_entry.insert(0, self.db_config.get('password', ''))

        ttk.Label(config_frame, text="Database:").grid(row=2, column=0, padx=5, pady=5)
        self.db_entry = ttk.Entry(config_frame)
        self.db_entry.grid(row=2, column=1, padx=5, pady=5)
        self.db_entry.insert(0, self.db_config.get('database', ''))

        ttk.Button(config_frame, text="连接测试", command=self.test_connection).grid(row=2, column=2, columnspan=2, pady=5)

        # 表选择区域
        table_frame = ttk.LabelFrame(self.root, text="表选择", padding=10)
        table_frame.pack(fill="both", expand=True, padx=10, pady=5)

        # 添加搜索输入框和获取表按钮（左右结构）
        search_frame = ttk.Frame(table_frame)
        search_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky="ew")

        ttk.Button(search_frame, text="获取表", command=self.refresh_tables).pack(side="left", padx=5)
        ttk.Label(search_frame, text="搜索表名:").pack(side="left", padx=5)
        self.search_entry = ttk.Entry(search_frame)
        self.search_entry.pack(side="left", fill="x", expand=True)
        self.search_entry.bind("<KeyRelease>", self.on_search_change)

        # 表选择滚动区域
        self.table_canvas = tk.Canvas(table_frame)
        self.table_scrollbar = ttk.Scrollbar(table_frame, orient="vertical", command=self.table_canvas.yview)
        self.table_scrollbar.grid(row=1, column=1, sticky="ns", pady=5)
        self.table_canvas.grid(row=1, column=0, sticky="nsew", pady=5)
        self.table_canvas.configure(yscrollcommand=self.table_scrollbar.set)

        # 内部框架用于放置勾选框
        self.table_inner_frame = ttk.Frame(self.table_canvas)
        self.table_canvas.create_window((0, 0), window=self.table_inner_frame, anchor="nw")

        # 绑定鼠标滚轮事件
        self.table_canvas.bind_all("<MouseWheel>", self._on_mousewheel)  # Windows
        self.table_canvas.bind_all("<Button-4>", self._on_mousewheel)   # Linux
        self.table_canvas.bind_all("<Button-5>", self._on_mousewheel)   # Linux

        # 配置画布滚动区域
        table_frame.grid_rowconfigure(1, weight=1)
        table_frame.grid_columnconfigure(0, weight=1)

        ttk.Label(table_frame, text="或输入表名:").grid(row=2, column=0, padx=5, pady=5, sticky="w")
        self.table_entry = ttk.Entry(table_frame)
        self.table_entry.grid(row=3, column=0, padx=5, pady=5, sticky="ew")

        # 模板选择区域
        template_frame = ttk.LabelFrame(self.root, text="生成选项", padding=10)
        template_frame.pack(fill="x", padx=10, pady=5)

        self.template_vars = {
            'bean': tk.BooleanVar(value=True),
            'mapper': tk.BooleanVar(value=True),
            'controller': tk.BooleanVar(value=True),
            'service': tk.BooleanVar(value=True),
            'serviceImpl': tk.BooleanVar(value=True),
            'xml': tk.BooleanVar(value=True)
        }

        self.checkbuttons = {}
        for idx, (key, var) in enumerate(self.template_vars.items()):
            cb = ttk.Checkbutton(template_frame, text=key.capitalize(), variable=var)
            cb.grid(row=0, column=idx, padx=5, pady=5)
            cb.bind("<Enter>", lambda event, k=key: self.show_preview(event, k))
            cb.bind("<Leave>", self.hide_preview)
            self.checkbuttons[key] = cb

        # 输出配置
        output_frame = ttk.LabelFrame(self.root, text="输出配置", padding=10)
        output_frame.pack(fill="x", padx=10, pady=5)

        ttk.Label(output_frame, text="包名:").grid(row=0, column=0, padx=5, pady=5)
        self.package_entry = ttk.Entry(output_frame)
        self.package_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")  # 动态拉伸
        self.package_entry.insert(0, "com.example.demo")

        ttk.Label(output_frame, text="作者:").grid(row=0, column=2, padx=5, pady=5)
        self.author_entry = ttk.Entry(output_frame)
        self.author_entry.grid(row=0, column=3, padx=5, pady=5, sticky="ew")  # 动态拉伸
        self.author_entry.insert(0, self.author)

        ttk.Label(output_frame, text="输出目录:").grid(row=1, column=0, padx=5, pady=5)
        self.output_entry = ttk.Entry(output_frame)
        self.output_entry.grid(row=1, column=1, columnspan=3, padx=5, pady=5, sticky="ew")  # 占满剩余列
        self.output_entry.insert(0, "/Users/wangbingbing/Downloads/generated")

        # 设置列权重，确保动态拉伸
        output_frame.grid_columnconfigure(1, weight=1)
        output_frame.grid_columnconfigure(3, weight=1)

        # 生成按钮
        ttk.Button(self.root, text="生成代码", command=self.generate_code).pack(pady=10)

    def _on_mousewheel(self, event):
        """处理鼠标滚轮事件"""
        if event.num == 4 or event.delta > 0:  # 向上滚动
            self.table_canvas.yview_scroll(-1, "units")
        elif event.num == 5 or event.delta < 0:  # 向下滚动
            self.table_canvas.yview_scroll(1, "units")

    def on_search_change(self, event):
        """处理搜索输入变化，带防抖"""
        if self.search_debounce_id:
            self.root.after_cancel(self.search_debounce_id)
        self.search_debounce_id = self.root.after(300, self.filter_tables)

    def filter_tables(self):
        """根据搜索输入过滤表"""
        search_text = self.search_entry.get().strip().lower()
        filtered_tables = [
            (table_name, table_comment) for table_name, table_comment in self.all_tables
            if search_text in table_name.lower() or (table_comment and search_text in table_comment.lower())
        ]
        self.update_table_display(filtered_tables)

    def update_table_display(self, table_data):
        """更新表显示"""
        for widget in self.table_inner_frame.winfo_children():
            widget.destroy()

        current_selections = {table_name: var.get() for table_name, var in self.table_vars.items()}
        self.table_vars.clear()

        for idx, (table_name, table_comment) in enumerate(table_data):
            display_name = table_name
            if table_comment and table_comment.strip():
                display_name = f"{table_name} ({table_comment})"
            var = tk.BooleanVar(value=current_selections.get(table_name, False))
            cb = ttk.Checkbutton(self.table_inner_frame, text=display_name, variable=var)
            cb.grid(row=idx // 2, column=idx % 2, padx=5, pady=2, sticky="w")
            self.table_vars[table_name] = var

        self.table_inner_frame.update_idletasks()
        self.table_canvas.config(scrollregion=self.table_canvas.bbox("all"))

    def test_connection(self):
        """测试数据库连接"""
        self.update_db_config()
        try:
            conn = mysql.connector.connect(**self.db_config)
            conn.close()
            messagebox.showinfo("成功", "数据库连接成功！")
            self.save_config()
        except Exception as e:
            messagebox.showerror("错误", f"连接失败: {str(e)}")

    def refresh_tables(self):
        """刷新表列表，显示表名和表注释，使用勾选框，每行两个"""
        self.update_db_config()
        try:
            self.connect_db()
            self.cursor.execute("SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s", (self.db_config['database'],))
            self.all_tables = self.cursor.fetchall()
            self.close_db()
            self.update_table_display(self.all_tables)
            self.search_entry.delete(0, tk.END)
        except Exception as e:
            messagebox.showerror("错误", f"获取表失败: {str(e)}")

    def update_db_config(self):
        """更新数据库配置"""
        self.db_config = {
            'host': self.host_entry.get(),
            'port': int(self.port_entry.get()),
            'user': self.user_entry.get(),
            'password': self.pass_entry.get(),
            'database': self.db_entry.get()
        }

    def connect_db(self):
        self.conn = mysql.connector.connect(**self.db_config)
        self.cursor = self.conn.cursor()

    def close_db(self):
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()

    def to_camel_case(self, name):
        """将下划线分隔的名称转换为 Java 驼峰命名规范"""
        if not name:
            return ""
        parts = [part for part in name.split('_') if part]
        if not parts:
            return ""
        return parts[0].lower() + ''.join(part.capitalize() for part in parts[1:])

    def to_class_name(self, table_name):
        return ''.join(part.capitalize() for part in table_name.split('_'))

    def load_template(self, template_name):
        if getattr(sys, 'frozen', False):  # 如果是打包后的环境
            base_path = sys._MEIPASS  # PyInstaller 临时解压路径
        else:
            base_path = os.path.dirname(__file__)  # 开发环境
        template_path = os.path.join(base_path, 'templates', f"{template_name}.txt")
        with open(template_path, 'r', encoding='utf-8') as f:
            return f.read()

    def generate_fields(self, columns):
        type_mapping = {
            'varchar': 'String', 'char': 'String', 'text': 'String',
            'int': 'Integer', 'bigint': 'Long', 'tinyint': 'Integer',
            'decimal': 'BigDecimal', 'date': 'LocalDate',
            'datetime': 'LocalDateTime', 'timestamp': 'LocalDateTime'
        }
        fields = ""
        for column in columns:
            field_name, field_type = column[0], column[1].decode() if isinstance(column[1], bytes) else column[1]
            base_type = field_type.split('(')[0].lower()
            java_type = type_mapping.get(base_type, 'String')
            comment = column[8] if column[8] else ""
            if comment:
                fields += f"    /**\n     * {comment}\n     */\n"
            fields += f"    private {java_type} {self.to_camel_case(field_name)};\n\n"
        return fields.rstrip()

    def generate_xml_base_columns(self, columns):
        column_names = [col[0] for col in columns]
        return "        " + ", ".join(column_names)

    def generate_xml_result_map(self, columns, package_name, class_name):
        lines = []
        primary_keys = [col for col in columns if (len(col) > 4 and col[4] == 'PRI')]
        primary_key_field = primary_keys[0][0] if primary_keys else None
        for col in columns:
            column_name = col[0]
            property_name = self.to_camel_case(column_name)
            if primary_key_field and column_name == primary_key_field:
                lines.append(f"        <id column=\"{column_name}\" property=\"{property_name}\" />")
            else:
                lines.append(f"        <result column=\"{column_name}\" property=\"{property_name}\" />")
        return "\n".join(lines)

    def generate_xml_content(self, columns, package_name, class_name, table_name):
        template = self.load_template('xml_template')
        base_columns = self.generate_xml_base_columns(columns)
        result_map = self.generate_xml_result_map(columns, package_name, class_name)
        primary_keys = [col for col in columns if (len(col) > 4 and col[4] == 'PRI')]
        pk_field = primary_keys[0][0] if primary_keys else 'id'
        # 构造 where 条件占位，包含常见过滤
        where_conditions = (
            "            <if test=\"name != null and name != ''\">\n"
            "                AND t.name LIKE CONCAT('%', #{name}, '%')\n"
            "            </if>\n"
            "            <if test=\"startTime != null and startTime != ''\">\n"
            "                AND t.create_time &gt;= #{startTime}\n"
            "            </if>\n"
            "            <if test=\"endTime != null and endTime != ''\">\n"
            "                AND t.create_time &lt;= #{endTime}\n"
            "            </if>\n"
            "            <if test=\"status != null\">\n"
            "                AND t.status = #{status}\n"
            "            </if>\n"
        )

        content = template.replace('{{package_name}}', package_name) \
            .replace('{{class_name}}', class_name) \
            .replace('{{table_name}}', table_name) \
            .replace('{{base_columns}}', base_columns) \
            .replace('{{result_map}}', result_map) \
            .replace('{{pk_field}}', pk_field) \
            .replace('{{where_conditions}}', where_conditions) \
            .replace('{{author}}', self.author) \
            .replace('{{current_date}}', datetime.now().strftime('%Y-%m-%d'))
        return content

    def generate_file(self, template_name, table_name, package_name, output_dir, author, error_queue):
        """生成单个文件，多线程安全"""
        try:
            conn = mysql.connector.connect(**self.db_config)
            cursor = conn.cursor()
            cursor.execute(f"SHOW FULL COLUMNS FROM {table_name}")
            columns = cursor.fetchall()
            cursor.close()
            conn.close()

            class_name = self.to_class_name(table_name)
            lower_class_name = class_name[0].lower() + class_name[1:]
            template = self.load_template(template_name)

            content = template.replace('{{package_name}}', package_name) \
                .replace('{{class_name}}', class_name) \
                .replace('{{lower_class_name}}', lower_class_name) \
                .replace('{{table_name}}', table_name) \
                .replace('{{author}}', author) \
                .replace('{{current_date}}', datetime.now().strftime('%Y-%m-%d'))

            if template_name == 'bean_template':
                content = content.replace('{{fields}}', self.generate_fields(columns))
            elif template_name == 'xml_template':
                content = self.generate_xml_content(columns, package_name, class_name, table_name)

            dir_mapping = {
                'bean_template': 'entity', 'mapper_template': 'mapper',
                'controller_template': 'controller', 'service_template': 'service',
                'service_impl_template': 'service/impl', 'xml_template': 'resources/mapper'
            }
            output_subdir = dir_mapping.get(template_name, '')
            output_path = f"{output_dir}/{package_name.replace('.', '/')}/{output_subdir}"
            os.makedirs(output_path, exist_ok=True)

            if template_name == 'xml_template':
                file_name = f"{class_name}Mapper.xml"
            else:
                file_name = f"{class_name}{'ServiceImpl' if template_name == 'service_impl_template' else ''}{'Controller' if template_name == 'controller_template' else ''}{'Service' if template_name == 'service_template' else ''}{'Mapper' if template_name == 'mapper_template' else ''}.java"
            file_path = f"{output_path}/{file_name}"

            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)
            print(f"已生成文件: {file_path}")
        except Exception as e:
            error_queue.put(f"生成 {table_name} 的 {template_name} 失败: {str(e)}")

    def generate_code(self):
        """使用多线程生成代码"""
        self.update_db_config()
        input_tables = [t.strip() for t in self.table_entry.get().split(',') if t.strip()]
        selected_tables = [table_name for table_name, var in self.table_vars.items() if var.get()]
        table_names = list(set(input_tables + selected_tables))
        package_name = self.package_entry.get()
        output_dir = self.output_entry.get()
        author = self.author_entry.get()

        if not table_names:
            messagebox.showerror("错误", "请选择至少一个表或输入表名！")
            return

        template_mapping = {
            'bean': 'bean_template', 'mapper': 'mapper_template',
            'controller': 'controller_template', 'service': 'service_template',
            'serviceImpl': 'service_impl_template', 'xml': 'xml_template'
        }

        selected_templates = [k for k, v in self.template_vars.items() if v.get()]
        if not selected_templates:
            messagebox.showerror("错误", "请至少选择一个生成类型！")
            return

        error_queue = Queue()
        threads = []

        for table_name in table_names:
            for gen_type in selected_templates:
                t = threading.Thread(
                    target=self.generate_file,
                    args=(template_mapping[gen_type], table_name, package_name, output_dir, author, error_queue)
                )
                threads.append(t)
                t.start()

        for t in threads:
            t.join()

        errors = []
        while not error_queue.empty():
            errors.append(error_queue.get())

        if errors:
            messagebox.showerror("错误", "\n".join(errors))
        else:
            messagebox.showinfo("成功", "代码生成完成！")
            self.save_config()

    def show_preview(self, event, template_type):
        """显示代码预览"""
        input_tables = [t.strip() for t in self.table_entry.get().split(',') if t.strip()]
        selected_tables = [table_name for table_name, var in self.table_vars.items() if var.get()]
        table_name = input_tables[0] if input_tables else (selected_tables[0] if selected_tables else "")
        if not table_name:
            return

        self.update_db_config()
        package_name = self.package_entry.get()
        author = self.author_entry.get()

        template_mapping = {
            'bean': 'bean_template', 'mapper': 'mapper_template',
            'controller': 'controller_template', 'service': 'service_template',
            'serviceImpl': 'service_impl_template', 'xml': 'xml_template'
        }
        try:
            content = self.generate_preview_content(template_mapping[template_type], table_name, package_name, author)
        except Exception as e:
            return

        if self.preview_window:
            self.preview_window.destroy()

        self.preview_window = Toplevel(self.root)
        self.preview_window.overrideredirect(True)
        self.preview_window.geometry(f"400x300+{event.x_root + 20}+{event.y_root + 20}")

        preview_text = tk.Text(self.preview_window, wrap="none", height=20, width=50)
        preview_text.pack(fill="both", expand=True)

        formatted_content = self.format_code(content)
        preview_text.insert("1.0", formatted_content)
        preview_text.config(state="disabled")

    def hide_preview(self, event):
        """隐藏预览窗口"""
        if self.preview_window:
            self.preview_window.destroy()
            self.preview_window = None

    def generate_preview_content(self, template_name, table_name, package_name, author):
        """生成预览内容，不写入文件"""
        self.connect_db()
        self.cursor.execute(f"SHOW FULL COLUMNS FROM {table_name}")
        columns = self.cursor.fetchall()
        self.close_db()

        class_name = self.to_class_name(table_name)
        lower_class_name = class_name[0].lower() + class_name[1:]
        template = self.load_template(template_name)

        content = template.replace('{{package_name}}', package_name) \
            .replace('{{class_name}}', class_name) \
            .replace('{{lower_class_name}}', lower_class_name) \
            .replace('{{table_name}}', table_name) \
            .replace('{{author}}', author) \
            .replace('{{current_date}}', datetime.now().strftime('%Y-%m-%d'))

        if template_name == 'bean_template':
            content = content.replace('{{fields}}', self.generate_fields(columns))
        elif template_name == 'xml_template':
            content = self.generate_xml_content(columns, package_name, class_name, table_name)

        return content

    def format_code(self, content):
        """简单美化 Java 代码"""
        lines = content.split('\n')
        indent_level = 0
        formatted_lines = []
        indent = "    "

        for line in lines:
            stripped = line.strip()
            if not stripped:
                formatted_lines.append("")
                continue

            if stripped.startswith('}') or stripped.startswith(');'):
                indent_level = max(0, indent_level - 1)

            formatted_lines.append(indent * indent_level + stripped)

            if stripped.endswith('{'):
                indent_level += 1

        return '\n'.join(formatted_lines)


if __name__ == "__main__":
    root = tk.Tk()  # 使用默认样式
    app = CodeGeneratorApp(root)
    root.mainloop()